use crate::ast::{Ast, Expr, Pattern, Sign, Str, TopDef};
use itertools::Itertools;
use pest::{
    error::Error,
    iterators::{Pair, Pairs},
    Parser,
};

#[derive(pest_derive::Parser)]
#[grammar = "./syntax.pest"]
struct Syntax;

fn parse_name(pair: Pair<Rule>) -> &str {
    debug_assert_eq!(pair.as_rule(), Rule::Name);
    pair.as_str()
}

fn parse_string(pair: Pair<Rule>) -> Str {
    debug_assert_eq!(pair.as_rule(), Rule::String);
    let str = pair.as_str();
    Str(&str[1..str.len() - 1])
}

fn parse_sign(pair: Pair<Rule>) -> Sign {
    debug_assert_eq!(pair.as_rule(), Rule::Sign);
    let mut pairs = pair.into_inner();
    let (src_name, params) = {
        let pair = pairs.next().unwrap();
        debug_assert_eq!(pair.as_rule(), Rule::SignName);
        let mut pairs = pair.into_inner();
        let name = parse_name(pairs.next().unwrap());
        let params = pairs.at_most_one().unwrap().map(|pair| {
            debug_assert_eq!(pair.as_rule(), Rule::ParamsS);
            pair.into_inner()
                .inspect(|pair| debug_assert_eq!(pair.as_rule(), Rule::Wildcard))
                .count()
        });
        (name, params)
    };
    let out_name = pairs.at_most_one().unwrap().map(parse_name);
    Sign {
        src_name,
        out_name,
        params,
    }
}

fn parse_signs(pair: Pair<Rule>) -> Vec<Sign> {
    debug_assert_eq!(pair.as_rule(), Rule::Signs);
    pair.into_inner().map(parse_sign).collect()
}

fn parse_top_def(pair: Pair<Rule>) -> TopDef {
    match pair.as_rule() {
        Rule::DefValFn => {
            let mut pairs = pair.into_inner();
            let name = parse_name(pairs.next().unwrap());
            let params = pairs.take_while_ref(|pair| pair.as_rule() == Rule::Params);
            let params = params.at_most_one().unwrap();
            let params = params.map(|pair| pair.into_inner().map(parse_name).collect());
            let val = parse_expr(pairs.exactly_one().unwrap());
            TopDef::ValFn { name, params, val }
        }
        Rule::DefImport => {
            let (items, from) = pair.into_inner().collect_tuple().unwrap();
            let from = parse_string(from);
            let items = parse_signs(items);
            TopDef::Import { from, items }
        }
        _ => {
            eprintln!("{:?}", pair.as_rule());
            unreachable!()
        }
    }
}

fn parse_exprs(pairs: Pairs<Rule>) -> Vec<Expr> {
    pairs.map(parse_expr).collect()
}

fn parse_args(pair: Pair<Rule>) -> Vec<Expr> {
    assert_eq!(pair.as_rule(), Rule::Args);
    parse_exprs(pair.into_inner())
}

fn parse_case(pair: Pair<Rule>) -> (Pattern, Expr) {
    debug_assert_eq!(pair.as_rule(), Rule::Case);
    let (pat, val) = pair.into_inner().collect_tuple().unwrap();
    (parse_pattern(pat), parse_expr(val))
}

fn parse_expr(pair: Pair<Rule>) -> Expr {
    match pair.as_rule() {
        Rule::String => Expr::String(parse_string(pair)),
        Rule::Tuple => Expr::Tuple(parse_exprs(pair.into_inner())),
        Rule::Use => {
            let mut pairs = pair.into_inner();
            let name = parse_name(pairs.next().unwrap());
            let args = pairs.at_most_one().unwrap().map(parse_args);
            Expr::Use { name, args }
        }
        Rule::Let => {
            let mut pairs = pair.into_inner();
            let pat = parse_pattern(pairs.next().unwrap());
            let mut pairs = pairs.map(parse_expr).map(Box::new);
            let (val, then) = pairs.next_tuple().unwrap();
            let alt = pairs.at_most_one().unwrap();
            Expr::Let {
                pat,
                val,
                then,
                alt,
            }
        }
        Rule::Match => {
            let mut pairs = pair.into_inner();
            let val = Box::new(parse_expr(pairs.next().unwrap()));
            let case = pairs.map(parse_case).collect();
            Expr::Match { val, case }
        }
        _ => unreachable!(),
    }
}

fn parse_pattern(pair: Pair<Rule>) -> Pattern {
    match pair.as_rule() {
        Rule::Wildcard => Pattern::Wildcard,
        Rule::String => Pattern::String(parse_string(pair)),
        Rule::Name => Pattern::Name(parse_name(pair)),
        Rule::TupleP => Pattern::Tuple(pair.into_inner().map(parse_pattern).collect()),
        _ => unreachable!(),
    }
}

pub fn parse(input: &str) -> Result<Ast, Box<Error<Rule>>> {
    let mut pairs = Syntax::parse(Rule::All, input).map_err(Box::new)?;
    let eoi = pairs.next_back();
    debug_assert_eq!(eoi.unwrap().as_rule(), Rule::EOI);
    let exports = parse_signs(pairs.next_back().unwrap());
    let defs = pairs.map(parse_top_def).collect();
    Ok(Ast { defs, exports })
}
